關於 sync/atomic 是於 go@v1.14 才推出的新東西,筆者也是最近才知道有這個包可以使用。Atomic 目標在一些單純的操作上,完成最小原子性操作,使用上甚至比 mutex 更為簡便。
int32
uint32
int64
uint64
uintptr
unsafe.Pointer
支援的方法是依據資料型態提供,例如:AddInt32, AddInt64, AddUint64 ...,讓我們用一個簡單的範例看一下sync/atomic 的用法。
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
exampleCount()
}
func exampleCount() {
var count int64
timeStop := time.NewTimer(3 * time.Second)
//每100ms讀寫一次
go func() {
t := time.NewTicker(100 * time.Millisecond)
for {
select {
case <-t.C:
atomic.AddInt64(&count, 1)
fmt.Println(count)
}
}
}()
//每100ms讀寫一次
go func() {
t := time.NewTicker(100 * time.Millisecond)
for {
select {
case <-t.C:
atomic.AddInt64(&count, 1)
fmt.Println(count)
}
}
}()
select {
//預計執行 3s 60次
case <-timeStop.C:
time.Sleep(time.Millisecond) //防止最後一筆來不及寫入
if count == 60 {
fmt.Println("success")
} else {
fmt.Println("fail")
}
}
}
除了使用的方式外,我們當然也很在乎 atomic 的效能如何?以筆者自行測試的結果,速度優於 channel,約等同 mutex.Lock(),大家可以下載範例自行跑看看。根據筆者查到的資料,atomic package 底層是直接發送訊號跟 cpu 溝通,因此效能上會略優於在軟體層面實踐上鎖的 mutex package。但實測上差異實在是非常的小,如果不是對效能非常要求的服務,真的可以直接忽略效能的差異。
> go test -v -bench=. -run=none -benchmem ./basicGo/atomic/atomic_test.go
goos: darwin
goarch: amd64
BenchmarkAtomic
BenchmarkAtomic-4 5705864 199 ns/op 0 B/op 0 allocs/op
BenchmarkMutex
BenchmarkMutex-4 6138507 221 ns/op 0 B/op 0 allocs/op
BenchmarkChannel
BenchmarkChannel-4 2330770 637 ns/op 69 B/op 0 allocs/op
PASS
ok command-line-arguments 5.162s
從 Day9 之後我們主題圍繞在, Concurrency(併發) 與 Race Condiction 上,為了處理內部同步問題,我們學習了 channel, mutex, atomic 三種方法,來幫助我們在併發的過程保持 memory consistency,避免發生不符合預期的資料產生。在我們會開始實作之前,會先介紹 docker 的使用方式,方便我們建立各種資料庫,也順便補充一些關於 container 的概念,以利後面篇幅的進行。